using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Data;
using System.IO;
using Microsoft.CSharp;

namespace Lib.Views
{
    public class GenerateStubCodeCommand
    {
        private const string InitRowsMethod = "InitRows";
        private const string InitColumnsMethod = "InitColumns";
        private IndentedTextWriter tw;
        CodeCompileUnit cu;

        private DataTable _source;

        public GenerateStubCodeCommand(DataTable source)
        {
            GenerateCodeTree(source);
        }

        public void Execute(TextWriter output)
        {
            tw = new IndentedTextWriter(output, "\t");
            CSharpCodeProvider provider = new CSharpCodeProvider();
            provider.GenerateCodeFromCompileUnit(cu, tw, new CodeGeneratorOptions());
        }

        void GenerateCodeTree(DataTable source)
        {
            _source = source;

            cu = new CodeCompileUnit();

            RegisterNamespace();
        }

        void RegisterNamespace()
        {
            CodeNamespace ns = new CodeNamespace("DataStubs");
            cu.Namespaces.Add(ns);

            if (_source != null)
                ns.Types.Add(CreateDataStubClass());
        }


        CodeTypeDeclaration CreateDataStubClass()
        {
            CodeTypeDeclaration dataStubClass =
                new CodeTypeDeclaration(string.Format("{0}DataStub", _source.TableName));

            dataStubClass.BaseTypes.Add(new CodeTypeReference("System.Data.DataTable"));

            if (_source.Columns.Count > 0) {
                dataStubClass.Members.Add(CreateDefaultConstructor());
                dataStubClass.Members.Add(CreateInitColumnsMethod());
                if(_source.Rows.Count > 0)
                    dataStubClass.Members.Add(CreateInitRowsMethod());                
            }

            return dataStubClass;
        }

        CodeConstructor CreateDefaultConstructor()
        {
            CodeConstructor defaultConstructor = new CodeConstructor();
            defaultConstructor.Attributes = MemberAttributes.Public;

            if (_source.Columns.Count > 0)
                defaultConstructor.Statements.Add(new CodeMethodInvokeExpression(null,InitColumnsMethod));
            
            if (_source.Rows.Count > 0)
                defaultConstructor.Statements.Add(new CodeMethodInvokeExpression(null, InitRowsMethod));

            return defaultConstructor;
        }

        private CodeMemberMethod CreateInitColumnsMethod()
        {
            CodeMemberMethod initColumns = new CodeMemberMethod();
            initColumns.Name = InitColumnsMethod;

            foreach (DataColumn column in _source.Columns)
                initColumns.Statements.Add(GetAddColumn(column));

            return initColumns;
        }

        CodeMemberMethod CreateInitRowsMethod()
        {
            CodeMemberMethod initRowsMethod = new CodeMemberMethod();
            initRowsMethod.Name = InitRowsMethod;

            foreach (DataRow row in _source.Rows)
                initRowsMethod.Statements.Add(GetAddRow(row));

            return initRowsMethod;
        }

        private CodeMethodInvokeExpression GetAddRow(DataRow row)
        {
            CodeMethodInvokeExpression addRow = 
                new CodeMethodInvokeExpression(
                    new CodePropertyReferenceExpression(null,"Rows"),
                    "Add");

            foreach (DataColumn col in _source.Columns) {
							if (row[col] == null)
								addRow.Parameters.Add(new CodePrimitiveExpression(null));
							else if (row[col] is DBNull)
								addRow.Parameters.Add(new CodePropertyReferenceExpression(new CodeTypeReferenceExpression(typeof(DBNull)), "Value"));
							else {
								if (col.DataType.IsPrimitive || col.DataType.Name == typeof(string).Name)
									addRow.Parameters.Add(new CodePrimitiveExpression(row[col.ColumnName]));
								else if (IsParsable(col.DataType)) {
									addRow.Parameters.Add(
										new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(col.DataType), "Parse",
																									 new CodeExpression[] { new CodePrimitiveExpression(row[col.ColumnName].ToString()) }));
								} else
									addRow.Parameters.Add(
										new CodeObjectCreateExpression(col.DataType,
																									 new CodeExpression[] { new CodePrimitiveExpression(row[col.ColumnName].ToString()) }));
							}
            }
            return addRow;
        }

    	private static bool IsParsable(Type dataType) {
				Type[] parsableTypes = new Type[] {
					typeof(DateTime),
          typeof(decimal),
					typeof(double),
					typeof(float)
				};
    		foreach (Type type in parsableTypes) {
					if (dataType.Name == type.Name) return true;
    		}

    		return false;
    	}

    	private static CodeMethodInvokeExpression GetAddColumn(DataColumn column) {
            CodeMethodInvokeExpression addColumn = 
                new CodeMethodInvokeExpression(
                    new CodePropertyReferenceExpression(null, "Columns"),
                    "Add");

            addColumn.Parameters.Add(new CodePrimitiveExpression(column.ColumnName));
            addColumn.Parameters.Add(new CodeTypeOfExpression(column.DataType.Name));
            return addColumn;
        }


    }
}
